/**
 * Define a global symbol as the namespace.
 * All global variables and utility functions are stored
 * as the properties of this namespace.
 */
var Torch = {};

//Constants
/**
 * Root url.
 */
Torch.URL_D = window.location.pathname.replace(/((\/\w+)+\/)\w+\.\w+/g,"$1data/");

/**
 * JSON Data url.
 */
Torch.NAME_DATA = "TorchRelayData.json";

Torch.NAME_INFO = "TorchInfoData.json";

Torch.NAME_VIDEO = "TorchVideoList.txt";

/**
 * World city type.
 */
Torch.TYPE_WDCT = "w";

/**
 * China province type.
 */
Torch.TYPE_CNPV = "cp";

/**
 * China city type.
 */
Torch.TYPE_CNCT = "c";
Torch.TYPE_MTCT = "mt";

/**
 * Torch has passed status.
 */
Torch.STATUS_Y = 1;

/**
 * Torch still not pass status.
 */
Torch.STATUS_N = 2;

/**
 * World region.
 */
Torch.REGION_WD = 1;

/**
 * China region.
 */
Torch.REGION_CN = 2;

Torch.REGION_CN_MT = 3;

/**
 * Event from Map.
 */
Torch.EVENT_M = 1;

/**
 * Event from Panel.
 */
Torch.EVENT_P = 2;

/**
 * Scroll up.
 */
Torch.SCROLL_U = 1;

/**
 * Scroll down.
 */
Torch.SCROLL_D = 2;

/**
 * Player traverse delay.
 */
Torch.PLAYER_DELAY = 5000;

/**
 * Torch icon url.
 */
Torch.URL_ICON_T = "../images/olympic-torch-24.png";

/**
 * Gray dot icon url.
 */
Torch.URL_ICON_G = "../images/dot-gray-transparent.png";

/**
 * Red dot icon url.
 */
Torch.URL_ICON_R = "../images/dot-red-transparent.png";

/**
 * Gray color used for polyline.
 */
Torch.COLOR_G = "#333333";

/**
 * Red color used for polyline.
 */
Torch.COLOR_R = "#ff0000";

/**
 * The default map level of world view.
 */
Torch.LEVEL_WD_DF = 2;

/**
 * The default map level of China view.
 * Often to show all provinces.
 */
Torch.LEVEL_CN_DF = 4;

/**
 * The default map level of China province view.
 * Often to show all cities of a province.
 */
Torch.LEVEL_PV_DF = 8;

/**
 * The map level range to show the overlays of the world. 
 */
Torch.LEVEL_WD = [1, 10];

/**
 * The map level range to show the overlays of China.
 */
Torch.LEVEL_CN = [4, 17];

/**
 * The map level range to show the overlays of China province.
 */
Torch.LEVEL_CN_PV = [4, 5];

/**
 * The map level range to show the overlays of China city.
 */
Torch.LEVEL_CN_CT = [6, 17];

/**
 * The range flag of the world map level.
 */
Torch.RANGE_WD = 1;

/**
 * The range flag of the world map level.
 */
Torch.RANGE_CN = 2;

/**
 * The range flag of the world map level.
 */
Torch.RANGE_CN_PV = 3;

/**
 * The range flag of the world map level.
 */
Torch.RANGE_CN_CT = 4;

// Global variables.
Torch.panel = new TPanel();

Torch.worldMap = null;

Torch.chinaMap = null;

/**
 * World city list.
 */
Torch.wdctList = null;

/**
 * China city list.
 */
Torch.cnctList = null;

/**
 * China province list.
 */
Torch.cnpvList = null;

/**
 * Torch infomation list.
 */
Torch.infoList = null;

Torch.videoList = null;

/**
 * Default world city.
 */
Torch.wCity = null;

/**
 * Default china city.
 */
Torch.cCity = null;

/**
 * Current torch city.
 */
Torch.tCity = null;

/**
 * Current region.
 */
Torch.curRegion = -1;

/**
 * Current selected city.
 */
Torch.curCity = null;

Torch.curMap = null;

/**
 * Player play interval hook.
 */
Torch.playId = null;

/**
 * Scroll bar auto acroll hook.
 */
Torch.scrollId = null;

/**
 * 
 */
Torch.pr = ["http://mw2.google.com/mw-panoramio/photos/medium/",
          "http://static.panoramio.com/photos/original/",
          "http://www.panoramio.com/photos/original/"];
    
Torch.tvU = "http://huoju.cctv.com/";
Torch.rU = Torch.tvU + "route.php?cityid=";
Torch.vU = Torch.tvU + "video.php?cityid=";
Torch.aU = Torch.tvU + "album.php?cityid=";

Torch.pa = "um=1&ie=UTF-8&sa=N&tab=iw&sourceid=tr_map&q=";
Torch.pgU = "http://www.google.cn/search?";
Torch.imgU = "http://images.google.cn/images?";
Torch.videoU = "http://video.google.cn/videosearch?";
Torch.lU = "http://shenghuo.google.cn/shenghuo/search?a_n1=%E5%9F%8E%E5%B8%82&a_y1=1&a_o1=0&a_v1=";


// Utility functions.
/**
 * Find the html element by id.
 */
Torch.elem = function(id) {
  return document.getElementById(id);
}

Torch.newEl = function(tag, style, parent) {
  var node = document.createElement(tag);
  node.className = style;
  if (parent) {
    parent.appendChild(node);
  }
  return node;
}

Torch.newTextEl = function(text, parent) {
  parent.appendChild(document.createTextNode(text));
}

/**
 * Find the city object from the corresponding list by id.
 */
Torch.city = function(id) {
  if(!id)
    return null;
  var r = id.match(/(\D+)(\d+)/);
  var idx = parseInt(r[2]);
  switch (r[1]) {
    case Torch.TYPE_WDCT :
      return Torch.wdctList[idx];
    case Torch.TYPE_CNPV :
      return Torch.cnpvList[idx];
    case Torch.TYPE_CNCT :
      return Torch.cnctList[idx];
    default :
      return Torch.mtctList[idx];
  }
}

/**
 * Find the city list by region code. Don't include province list;
 */
Torch.list = function(region) {
  return (region == Torch.REGION_WD) ? Torch.wdctList : Torch.cnctList;
}

/**
 * Get city id by its type and index.
 */
Torch.cityId = function(type, idx) {
  return type + idx;
}

/**
 * The player function.
 */
Torch.play = function() {
  var city = Torch.curCity;
  if (Torch.curCity.id.search(/^cp/) == 0) {
    var el = Torch.elem(Torch.curCity.id).nextSibling.firstChild;
    city = Torch.curCity = Torch.cnctList[parseInt(el.id.match(/\d+/))];
  }
  Torch.curMap.setCity(city);
  Torch.panel.setCity(city);

  Torch.playId = setInterval("Torch.toNext(0);", Torch.PLAYER_DELAY);
  Torch.elem("playButton").style.display = "none";
  Torch.elem("pauseButton").style.display = "block";
}

/**
 * The player function.
 */
Torch.stop = function() {
  clearInterval(Torch.playId);
  Torch.playId == null;
}

/**
 * The player function.
 */
Torch.pause = function() {
  Torch.stop();
  Torch.elem("playButton").style.display = "block";
  Torch.elem("pauseButton").style.display = "none";
}

/**
 * The player function.
 */
Torch.previous = function() {
  Torch.pause();
  Torch.toNext(1);
}

/**
 * The player function.
 */
Torch.next = function() {
  Torch.pause();
  Torch.toNext(0);
}

/**
 * The player help function.
 * 
 * @param {int}
 *            isAscend Flag to determine which direction player should go.
 *            1:ascend; 0:descend.
 */
Torch.toNext = function(isAscend) {
  var idx = parseInt(Torch.curCity.id.match(/\d+/));
  var n;
  switch (Torch.curCity.type) {
    case Torch.TYPE_CNPV :
      var el = Torch.elem(Torch.curCity.id).nextSibling.firstChild;
      idx = parseInt(el.id.match(/\d+/));
      n = Torch.cnctList[isAscend ? (idx - 1) : idx];
      break;
    case Torch.TYPE_CNCT :
      if ((idx == 0) && isAscend) {
        Torch.setRegion(Torch.REGION_WD, Torch.EVENT_P);
        n = Torch.wdctList[Torch.wdctList.length-1];
      }else{
        n = Torch.cnctList[isAscend ? idx -1 : idx +1];
      }
      break;
    case Torch.TYPE_WDCT:
      if ((idx == Torch.wdctList.length-1) && !isAscend) {
        Torch.setRegion(Torch.REGION_CN, Torch.EVENT_P);
        n = Torch.cnctList [0];
      }else{
        n = Torch.wdctList[isAscend ? idx -1 : idx +1];
      }
      break;
  }
  if (n) {
    Torch.curCity = n;
    Torch.curMap.setCity(n);
    Torch.panel.setCity(n);
  }
}

/**
 * Junction point of setting city betweent map and panel.
 * @param {City} city New city.
 * @param {int} eventSrc Event source.
 */
Torch.setCity = function(city, eventSrc) {
  if (eventSrc == Torch.EVENT_P) {
    if((city.type == Torch.TYPE_WDCT && Torch.curRegion != Torch.REGION_WD) ||
       (city.type != Torch.TYPE_WDCT && Torch.curRegion == Torch.REGION_WD)) {
      var region = (Torch.curRegion == Torch.REGION_WD) 
                   ? Torch.REGION_CN : Torch.REGION_WD;
      Torch.setRegion(region);
    }
    Torch.curMap.setCity(city);
  } else if (eventSrc == Torch.EVENT_M) {
    Torch.panel.setCity(city);
  }
  Torch.curCity = city;
}

/**
 * Junction point of setting region betweent map and panel.
 * @param {int} region
 * @param {int} whereComeFrom Indicate who change city initially.
 */
Torch.setRegion = function(region) {
  if(Torch.playId){
    Torch.pause();
  }
  Torch.curRegion = region;
  Torch.curMap.reset();
  Torch.setDispMap(region);
  Torch.panel.setRegion(region);
//  Torch.curCity = (region == Torch.REGION_WD) ? Torch.wCity : Torch.cCity;
}

Torch.setDispMap = function(region){
  switch(region){
    case Torch.REGION_WD:
      Torch.curMap = Torch.worldMap;
      Torch.elem("worldMap").style.width = "558px";
      Torch.elem("chinaMap").style.width = "0";
      break;
    case Torch.REGION_CN:
      Torch.curMap = Torch.chinaMap;
      Torch.elem("worldMap").style.width = "0";
      Torch.elem("chinaMap").style.width = "558px";
      Torch.curMap.hideSpecial();
      break;
    case Torch.REGION_CN_MT:
      Torch.curMap = Torch.chinaMap;
      Torch.elem("worldMap").style.width = "0";
      Torch.elem("chinaMap").style.width = "558px";
      Torch.curMap.showSpecial();
  }
}

/**
 * Transform date String to locale string.
 * @param {String} date
 */
Torch.toLocaleDateCN = function(date) {
  return date.replace(/(\d{2})(\d{2})/g,"$1月$2日");
}

Torch.setLocale = function(query){
  var lcObj;
  var index = query.indexOf('l=');
  if (index != -1 && (query.substr(index,7).indexOf('TW') != -1 
      || query.substr(index,7).indexOf('tw') != -1)) {
    lcObj = Torch.i18n_TW;
    Torch.NAME_DATA = "TorchRelayData_tw.json";
    Torch.NAME_INFO = "TorchInfoData_tw.json";
    Torch.NAME_VIDEO = "TorchVideoList_tw.txt";
    Torch.elem("apiL").href = "http://code.google.com/apis/maps/";
    if(query.indexOf('loc=hk') != -1) {
      Torch.elem("cp_lb2").href = "http://maps.google.com/maps?f=q&hl=en&q=hongkong&ie=UTF8&z=10";
      Torch.elem("cp_lb7").href = "http://maps.google.com/maps/mpl?moduleurl=http://www.google.com/mapfiles/mapplets/olympic2008/torch/torch.xml&hl=zh-TW&ll=22.40595,114.153442&spn=0.458331,0.725098";
      Torch.elem("cp_lb8").href = "http://google.com.hk/torchrelay/#earth";
    } else {
      Torch.elem("cp_lb2").href = "http://maps.google.com.tw";
      Torch.elem("cp_lb7").href = "http://maps.google.com.tw/maps/mpl?moduleurl=http://www.google.com/mapfiles/mapplets/olympic2008/torch/torch.xml";
      Torch.elem("cp_lb8").href = "http://google.com.tw/torchrelay/#earth" 
    }
    
    Torch.pa = "h1=zh-TW&" + Torch.pa;
    Torch.pgU = "http://www.google.com/search?";
    Torch.imgU = "http://images.google.com/images?";
    Torch.videoU = "http://video.google.com/videosearch?";
  } else {
    lcObj = Torch.i18n_CN;
    Torch.pa = "h1=zh-CN&" + Torch.pa;
  }
  for (var i in lcObj) {
    eval("Torch." + i + "='" + lcObj[i]+"';");
  }
  Torch.elem("scrollUp").innerHTML = Torch.upL;
  Torch.elem("scrollDown").innerHTML = Torch.downL;
  Torch.elem("worldLink").innerHTML = Torch.worldL;
  Torch.elem("chinaLink").innerHTML = Torch.chinaL;
  Torch.elem("mtLink").innerHTML = Torch.mtL;
  Torch.elem("cp_lb1").innerHTML = Torch.cp_lb1;
  Torch.elem("cp_lb2").innerHTML = Torch.cp_lb2;
  Torch.elem("cp_lb3").innerHTML = Torch.cp_lb3;
  Torch.elem("cp_lb4").innerHTML = Torch.cp_lb4;
  Torch.elem("cp_lb5").innerHTML = Torch.cp_lb5;
  Torch.elem("cp_lb6").innerHTML = Torch.cp_lb6;
  Torch.elem("cp_lb7").innerHTML = Torch.cp_lb7;
  Torch.elem("cp_lb8").innerHTML = Torch.cp_lb8;
  Torch.elem("cp_lb9").innerHTML = Torch.cp_lb9;
}

Torch.getListTop = function(){
  switch(Torch.curRegion){
    case Torch.REGION_WD:
      return Torch.elem("worldList");
    case Torch.REGION_CN:
      return Torch.elem("chinaList");
    case Torch.REGION_CN_MT:
      return Torch.elem("mtList");
  }
}
/**
 * Scroll the city list
 * @param dir The scroll direction.
 */
Torch.scroll = function(dir) {
  var top = Torch.getListTop();
  var elH = top.firstChild.offsetHeight;
  if (dir == Torch.SCROLL_U) {
    top.scrollTop = top.scrollTop - elH;
  } else if (dir == Torch.SCROLL_D) {
    top.scrollTop = top.scrollTop + elH;
  }
}

/**
 * Start auto scroll.
 * @param dir The scroll direction.
 */
Torch.startScroll = function(dir) {
  Torch.scroll(dir);
  Torch.scrollId = setInterval("Torch.scroll(" + dir + ");", 500);
}

/**
 * Stop auto scroll.
 */
Torch.stopScroll = function() {
  clearInterval(Torch.scrollId);
  Torch.scrollId == null;
}

/**
 * The start point of the whole.
 */
Torch.start = function() {
  if( !Torch.worldMap || !Torch.chinaMap){
    return;
  }
  var now = getTodayStr();
  var keyD = '0503';
  var sD = '0324';
  var eD = '0808';
  Torch.curRegion = (now <= keyD) ? Torch.REGION_WD : Torch.REGION_CN;
  Torch.setDispMap(Torch.curRegion);
  GDownloadUrl(Torch.URL_D + Torch.NAME_INFO, function(data){
    Torch.infoList = eval(data);
  });
  GDownloadUrl(Torch.URL_D + Torch.NAME_VIDEO, function(data){
    Torch.videoList = eval(data);
  });
  GDownloadUrl(Torch.URL_D + Torch.NAME_DATA, function(data) {
    var dataList = eval(data);
    Torch.wdctList = dataList[0].wdct;
    Torch.cnpvList = dataList[1].cnpv;
    Torch.cnctList = dataList[2].cnct;
    Torch.mtctList = dataList[3].mtct;
    if (Torch.curRegion == Torch.REGION_WD) { // Display world view by default
      if (now < sD) {
        Torch.curCity = Torch.wCity = Torch.wdctList[0];
        importCity(Torch.wdctList, 0, Torch.wdctList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_WDCT, Torch.worldMap);
        
      } else {
        var idx = findTorchCity(Torch.wdctList, now);
        Torch.curCity = Torch.wCity = Torch.tCity  = Torch.wdctList[idx];
        importCity(Torch.wdctList, 0, idx, Torch.STATUS_Y, Torch.TYPE_WDCT, Torch.worldMap);
        importCity(Torch.wdctList, idx + 1, Torch.wdctList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_WDCT, Torch.worldMap);
      }
      Torch.cCity = Torch.cnctList[0];
      importCity(Torch.cnpvList, 0, Torch.cnpvList.length - 1,
                 Torch.STATUS_N, Torch.TYPE_CNPV, Torch.chinaMap);
      importCity(Torch.cnctList, 0, Torch.cnctList.length - 1,
                 Torch.STATUS_N, Torch.TYPE_CNCT, Torch.chinaMap);
      
    } else { // Display China view by default, same logic as above
      Torch.wCity = Torch.wdctList[0];
      importCity(Torch.wdctList, 0, Torch.wdctList.length - 1,
                 Torch.STATUS_Y, Torch.TYPE_WDCT, Torch.worldMap);
      if (now > eD) {
        Torch.curCity = Torch.cCity = Torch.cnctList[0];
        importCity(Torch.cnpvList, 0, Torch.cnpvList.length - 1,
                   Torch.STATUS_Y, Torch.TYPE_CNPV, Torch.chinaMap);
        importCity(Torch.cnctList, 0, Torch.cnctList.length - 1,
                   Torch.STATUS_Y, Torch.TYPE_CNCT, Torch.chinaMap);
                     
      } else {
        var idx = findTorchCity(Torch.cnctList, now);
        Torch.curCity = Torch.cCity = Torch.tCity = Torch.cnctList[idx];
        var pIdx = parseInt(Torch.tCity.p);
        importCity(Torch.cnpvList, 0, pIdx, Torch.STATUS_Y, Torch.TYPE_CNPV, Torch.chinaMap);
        importCity(Torch.cnpvList, pIdx + 1, Torch.cnpvList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_CNPV, Torch.chinaMap);
        importCity(Torch.cnctList, 0, idx, Torch.STATUS_Y, Torch.TYPE_CNCT, Torch.chinaMap);
        importCity(Torch.cnctList, idx + 1, Torch.cnctList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_CNCT, Torch.chinaMap);
        
      }
    }
    importCity(Torch.mtctList, 0, Torch.mtctList.length - 1,
                 Torch.STATUS_Y, Torch.TYPE_MTCT, Torch.chinaMap);
                 
    Torch.panel.init(Torch.curRegion);
    Torch.worldMap.reset();
    Torch.chinaMap.reset();
    Torch.curMap.init();
  });

  /**
   * Private function of Torch.start().
   * Add cities into panel and map.
   * @param list The city list.
   * @param sIdx Start index in the list to import.
   * @param eIdx End index in the list to import.
   * @param status All import cities's status.
   * @param type All import cities's type.
   */
  function importCity(list, sIdx, eIdx, status, type, map) {
    Torch.panel.importCity(list, sIdx, eIdx, status, type);
    map.importCity(list, sIdx, eIdx, status, type);
  }
  
  /*
   * Private function of Torch.start().
   * Get current date string as format: mmdd.
   */
  function getTodayStr(){
    var now = new Date();
    var m = now.getMonth() + 1; // Function getMonth() is start from 0.
    var d = now.getDate();
    return (m < 10 ? "0" + m : m) + (d < 10 ? "0" + d : d);
  }

  /**
   * Private function of Torch.start().
   * Find the city where the torch should be currently.
   * @param {Array} cityList Array of cities.
   * @param {Date} now Current date object.
   * 
   * @return {int} The torch city's index.
   */
  function findTorchCity(list, now) {
    return quickSearch(now, 0, list.length - 1, list);
    
    /**
     * Private function. Binary search.
     */
    function quickSearch(today, sIdx, eIdx, list) {
      if (sIdx == eIdx) {
        var r = list[sIdx].d.match(/\d{4}/g);
        return (today >= r[0]) ? sIdx : (sIdx - 1);
      }
      var mIdx = Math.floor((eIdx + sIdx) / 2);
      var r = list[mIdx].d.match(/\d{4}/g);
      // The city date format is '****' or '**** - ****'. So
      // r[0] is the numbers of the city date or the date before '-', and
      // r[1] might be exist as the numbers of the date after '-'.
      if (today < r[0] && mIdx > sIdx) {
        return quickSearch(today, sIdx, --mIdx, list);
      } else if ((r[1] && (today > r[1] && mIdx < eIdx))
                 || (!r[1] && (today > r[0] && mIdx < eIdx))) {
        return quickSearch(today, ++mIdx, eIdx, list);
      } else {
        return mIdx;
      }
    }
  }
}

/**
 * A customs class. 
 * Used to display and control the torch relay scheduler.
 */
function TPanel() {
  /**
   * Private property.
   */
  var curCityId = null;

  /**
   * Public function of the class.
   * Show the new region in the panel. 
   * @param {int} region The new region.
   */
  this.setRegion = function(region) {
    var id = (region == Torch.RANGE_WD)? Torch.wCity.id : Torch.cCity.id;
    scrollToVisible(Torch.elem(id));
    // Restore the current city's style
    var curEl = Torch.elem(curCityId);
    var oldCt = Torch.city(curCityId);
    if (curEl) {
      var r = curEl.className.split(/\s*selected\s*/);
      curEl.className = r.join(" ");
    }
    if (oldCt) {
      if (oldCt.type == Torch.TYPE_CNPV) {
        collapseProv(curEl);
      } else if (oldCt.type == Torch.TYPE_CNCT) {
        collapseProv(Torch.elem(oldCt.parentId));
      } 
    }
    
    switch (region) {
      case Torch.REGION_WD :
        Torch.elem("worldLink").className = "active";
        Torch.elem("chinaLink").className = "";
        Torch.elem("mtLink").className = "";
        Torch.elem("worldList").style.display = "";
        Torch.elem("chinaList").style.display = "none";
        Torch.elem("mtList").style.display = "none";
        break;
      case Torch.REGION_CN :
        Torch.elem("worldLink").className = "";
        Torch.elem("chinaLink").className = "active";
        Torch.elem("mtLink").className = "";
        Torch.elem("worldList").style.display = "none";
        Torch.elem("chinaList").style.display = "";
        Torch.elem("mtList").style.display = "none";
        break;
      default:  //special everest
        Torch.elem("worldLink").className = "";
        Torch.elem("chinaLink").className = "";
        Torch.elem("mtLink").className = "active";
        Torch.elem("worldList").style.display = "none";
        Torch.elem("chinaList").style.display = "none";
        Torch.elem("mtList").style.display = "";
        curCityId = "mt5";
        Torch.elem("mt5").className = Torch.elem("mt5").className + " selected";
        break;
    }
 }

  /**
   * Private function of the class.
   * Change province node to expand/collapse according to its status.
   * @param {Eelment} elem The province's node.
   */
  function switchProv(elem) {
    if (elem.className.search(/hide/) != -1) {
      expandProv(elem);
    } else if (elem.className.search(/show/) != -1) {
      collapseProv(elem);
    }
  }

  /**
   * Private function of the class.
   * Expand the given province node.
   * @param {Eelment} el The province's node.
   */
  function expandProv(el) {
    el.className = el.className.replace(/hide/, "show");
    el.nextSibling.className = "show-prov";
  }
  
  /**
   * Private function of the class.
   * Collapse the given province node.
   * @param {Eelment} el The province's node.
   */
  function collapseProv(el) {
    el.className = el.className.replace(/show/, "hide");
    el.nextSibling.className = "hide-prov";
  }
  
  /**
   * Private function of the changeCity() function.
   * Auto scroll the city list.
   * @param {Element} ctEl
   */
  function scrollToVisible(ctEl) {
    var topPrt = Torch.getListTop();
    var prtH = topPrt.offsetHeight;
    
    if(ctEl.parentNode.className.search(/hide/) != -1){
      ctEl = ctEl.parentNode.previousSibling;
      topPrt.scrollTop = getPosition(ctEl);
//      return;
    }
    
    var elH = ctEl.offsetHeight;
    var srlH = Torch.elem("up").offsetHeight;
    var pos = getPosition(ctEl)-topPrt.scrollTop;
    if(document.all){//in IE, it ignore the up/down element offsetHeight
      pos += srlH;
      if (pos > prtH) {
        topPrt.scrollTop = ctEl.offsetTop - (prtH - elH);
      } else if (pos < 0) {
        topPrt.scrollTop = ctEl.offsetTop;
      }
    }else{
      pos -= 82;
      if ( (pos + elH) > prtH) {  // Below down scrollBar
        topPrt.scrollTop = topPrt.scrollTop + ((pos - prtH) + elH); 
      } else if (pos < 0) {  // Above up scrollBar
        topPrt.scrollTop = topPrt.scrollTop + pos;
      }
    }
     /**
     * Private function of scrollToVisible().
     * Calculate the elem's vertical position.
     * @param {Element} elem The target element to get position.
     * 
     * @return {int} The target element's vertival scroll height.
     */
    function getPosition(elem) {
      var h = 0;
      for (var e = elem;e && e != topPrt; e = e.offsetParent)
        h += e.offsetTop;
      for (e = elem.parentNode;e && e != topPrt; e = e.parentNode)
        if (e.scrollTop)
          h -= e.scrollTop;
      return h;
    }
  }

  /**
   * Private function of the class.
   * Change the new city to display in the panel.
   * @param {City} city City Object.
   */
  function changeCity(city) {
    // Restore the current city's style
//    if ((city.type == Torch.TYPE_WDCT && Torch.curRegion != Torch.REGION_WD)
//        || ((city.type == Torch.TYPE_CNCT || city.type == Torch.TYPE_CNPV) && Torch.curRegion != Torch.REGION_CN)
//        || (city.type == Torch.TYPE_MTCT && Torch.curRegion != Torch.REGION_CN_MT)) {
//      var region = (city.type == Torch.TYPE_WDCT) ? Torch.REGION_WD : Torch.REGION_CN;
//      region = (city.type == Torch.TYPE_MTCT) ? Torch.REGION_CN_MT : region;
//      Torch.setRegion(region);
//    } else {
      var curEl = Torch.elem(curCityId);
      var oldCt = Torch.city(curCityId);
      if (curEl) {
        var r = curEl.className.split(/\s*selected\s*/);
        curEl.className = r.join(" ");
      }
      if (oldCt) {
        if (oldCt.type == Torch.TYPE_CNPV) {
          collapseProv(curEl);
        } else if (oldCt.type == Torch.TYPE_CNCT 
                 && oldCt.parentId != city.parentId) {
          collapseProv(Torch.elem(oldCt.parentId));
        } 
      }
//    }
    
    // Highlight "new" current city.
    curCityId = city.id;
    curEl = Torch.elem(curCityId);
    var className = curEl.className;
    curEl.className = className + " selected";
    if (city.type == Torch.TYPE_CNPV) {
      switchProv(curEl);
    } else if (city.type == Torch.TYPE_CNCT) {
      var pEl = Torch.elem(city.parentId);
      if (pEl.className.search(/hide/) != -1) {
        expandProv(pEl);
      }
    } 
    scrollToVisible(curEl);
  }

  /**
   * Public function of the class.
   * Add cities into the panel.
   * @param {Array} list The list of cities.
   * @param {int} sIdx The start index thar where should be start.
   * @param {int} eIdx The end index that where should be stop.
   * @param {String} status The cities's status that added.
   * @param {String} type The cities's type that added.
   */
  this.importCity = function(list, sIdx, eIdx, status, type) {
    var s = (status == Torch.STATUS_N) ? "noTorch" : "torched";
    for (var i = sIdx; i <= eIdx; i++) {
      var c = list[i];
      c.type = type;
      c.id = Torch.cityId(type, i);
      var node = createCityNode(c, s);
      createNameNode(c.n, node);
      createDateNode(c.d, node, type);
      
    }
    
    /**
     * Private function of importCity().
     * Create the city node in the panel.
     * @param {City} city The city object.
     * @param {String} className The node's className.
     * 
     * @return {ELement} The created city node, without name and date children.
     */
    function createCityNode(city, className) {
      var cityNode = Torch.newEl("div", className);
      cityNode.setAttribute("id",city.id);
      var parent;
      switch (city.type) {
        case Torch.TYPE_WDCT :
          city.parentId = "worldList";
          parent = Torch.elem("worldList");
          parent.appendChild(cityNode);
          break;
        case Torch.TYPE_CNPV :
          city.parentId = "chinaList";
          cityNode.className = cityNode.className + " hide";
          parent = Torch.elem("chinaList");
          parent.appendChild(cityNode);
          // Create province wrapper
          Torch.newEl("div", "hide-prov", parent)
          break;
        case Torch.TYPE_CNCT :
          city.parentId = "cp" + city.p;
          parent = Torch.elem(city.parentId).nextSibling; // The "div" tag node
          parent.appendChild(cityNode);
          break;
        default: //special everest
          city.parentId = "mtList";
          parent = Torch.elem("mtList");
          parent.appendChild(cityNode);
      }
      return cityNode;
    }

    /**
     * Private function of importCity().
     * @param {String} name The city name.
     * @param {Element} cityNode The city node that the name should append.
     */
    function createNameNode(name, cityNode) {
      var node = Torch.newEl("a", "name", cityNode);
      node.setAttribute("href", "#"); // Make the ':hover' act
      node.appendChild(document.createTextNode(name));
      if(cityNode.id == "w0" || cityNode.id == "w1"){
        node.innerHTML = node.innerHTML.replace(/(\S+)(\(\S+\))/,'$1<span class="name-desc"> $2 </span>');
        c.n = c.n.replace(/(\S+)(\(\S+\))/,'$1');
      }
      node.onclick = onCityNameClick;
    }

    /**
     * Private function of importCity().
     * @param {String} date The torch relay date passing the city.
     * @param {Element} cityNode The city node that the name should append.
     */
    function createDateNode(date, cityNode, type) {
      var node = Torch.newEl("p", "date", cityNode);
      var value;
      if(type == Torch.TYPE_MTCT) value = date;
      else value = Torch.toLocaleDateCN(date);
      node.appendChild(document.createTextNode(value));
    }
    
    /**
     * Private function of importCity().
     * The listener of the name node's onClick event.
     */
    function onCityNameClick() {
      var city = Torch.city(this.parentNode.id);
      if (curCityId != city.id) {
        changeCity(city);
        Torch.setCity(city, Torch.EVENT_P);
      } else {
        Torch.setCity(city, Torch.EVENT_P);
        if(city.type == Torch.TYPE_CNPV){
          switchProv(this.parentNode);
        }
      } 
    }
  }

  /**
   * Public function of the class.
   * Change the current city in the panel.
   * @param {City} city The target city to go to.
   */
  this.setCity = function(city) {
    if (city.id != curCityId) {
      changeCity(city);
    }
  }

  /**
   * Public function of the class. Used for TPanel's initiation.
   * @param {int} region The region that panel should be start and display.
   */
  this.init = function(region) {
    switch (region) {
      case Torch.REGION_WD :
        Torch.elem("worldLink").className = "active";
        Torch.elem("chinaLink").className = "";
        Torch.elem("mtLink").className = "";
        Torch.elem("worldList").style.display = "";
        Torch.elem("chinaList").style.display = "none";
        Torch.elem("mtList").style.display = "none";
        break;
      case Torch.REGION_CN :
        Torch.elem("worldLink").className = "";
        Torch.elem("chinaLink").className = "active";
        Torch.elem("mtLink").className = "";
        Torch.elem("worldList").style.display = "none";
        Torch.elem("chinaList").style.display = "";
        Torch.elem("mtList").style.display = "none";
        break;
    }
    // Highlight "new" current city.
    curCityId = Torch.curCity.id;
    var curEl = Torch.elem(curCityId);
    curEl.className = curEl.className + " selected";
    if (Torch.curCity.type == Torch.TYPE_CNCT) {
      var pEl = Torch.elem(Torch.curCity.parentId);
      if (pEl.className.search(/hide/) != -1) {
        expandProv(pEl);
      }
    } 
    scrollToVisible(curEl);
  }
}